#ifdef WITH_OPENGL
#include "renderer_opengl.h"
#include "mesh.h"
#include "jobs.h"
#include "effect.h"
#include "camera.h"
#include "matrix.h"
#include "vertex.h"
#include "vector3.h"
#include "shader_catalog_cg_opengl.h"
#include "opengl.h"
#include "string_util.h"

#include <iostream>

using namespace krryn;
using namespace krryn::core;
using namespace krryn::system_imp;
using namespace krryn::system;
using namespace krryn::scene;
using namespace krryn::math;


renderer_opengl::renderer_opengl(job_manager *a_Manager, const viewport_settings &a_Viewport) 
: renderer(a_Manager), 
  m_Viewport(a_Viewport), 
  m_ProjectionMatrix(matrix::create_perspective_projection(1.f, 1000.f, 1, 0.75)),
  m_Camera(0)
{
}

renderer_opengl::~renderer_opengl(){
}

view::shader_catalog* renderer_opengl::get_shader_catalog(){
	if(m_Catalog == 0){
		m_Catalog = new shader_catalog_cg_opengl();
	}

	return m_Catalog;
}

void renderer_opengl::set_projection_matrix(const math::matrix &a_ProjectionMatrix){
	m_ProjectionMatrix = a_ProjectionMatrix;
}

void renderer_opengl::set_camera(view::camera *a_Camera){
	m_Camera = a_Camera;
}

void renderer_opengl::setup(){
	m_Extensions.swap(explode((char*)glGetString(GL_EXTENSIONS), " "));

	std::cout 
		<< "opengl version: " 
		<< (char*)glGetString(GL_VERSION)
		<< " (" << (char*)glGetString(GL_RENDERER) << ")"
		<< std::endl
#ifdef _DEBUG
		<< "opengl extensions supported: "
		<< std::endl
		<< " - " 
		<< implode(m_Extensions, "\n - ") 
		<< std::endl
#endif
		; // notice semi colon!

	std::vector<std::string> l_Failed = opengl_helper::load_extensions(m_Extensions);

#ifdef _DEBUG
	if(!l_Failed.empty()){
		std::cout 
			<< "notice: failed to load the following function pointers: "
			<< std::endl
			<< " - " << implode(l_Failed, "\n - ")
			<< std::endl;
	}
#endif

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	// math::matrix is row-major so we can do this
	glLoadMatrixf(m_ProjectionMatrix.cell);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LESS);

	// TODO: remove dummy light
	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);
	float p[] = {0, 0, 2};
	float c1[] = {1, 1, 1, 1};
	float c2[] = {1, 1, 1, 1};
	glLightfv(GL_LIGHT0, GL_POSITION, p);
	glLightfv(GL_LIGHT0, GL_AMBIENT, c1);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, c2);
}

void renderer_opengl::tear_down(){
}

void renderer_opengl::begin_scene(){
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	if(m_Camera){
		matrix l_ViewMatrix = m_Camera->view_matrix();
		glLoadMatrixf(l_ViewMatrix.cell);
	}

	glPushMatrix();
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_COLOR_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
}

void renderer_opengl::draw(scene::mesh *a_Mesh, const math::matrix &a_Transformation){
	typedef scene::mesh::effect_list_t effect_list_t;

	if(!a_Mesh)
		return;

	const effect_list_t &l_Effects = a_Mesh->get_effects();

	glPushMatrix();
	glMultMatrixf(a_Transformation.cell);

	for(effect_list_t::const_iterator i = l_Effects.begin(); i != l_Effects.end(); i++){
		(*i)->enable();
	}

	glInterleavedArrays(GL_T2F_C4F_N3F_V3F, 0, a_Mesh->get_vertices());
	glDrawElements(GL_TRIANGLES, (GLsizei)a_Mesh->get_index_count(), GL_UNSIGNED_INT, a_Mesh->get_indices());

	for(effect_list_t::const_iterator i = l_Effects.begin(); i != l_Effects.end(); i++){
		(*i)->disable();
	}

	glPopMatrix();
}

void renderer_opengl::end_scene(){
	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_COLOR_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	glPopMatrix();
	glFinish();
}
#endif